package com.cmsz.xpay.common.unionpay.sign;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.cmsz.xpay.common.exception.DefaultReturnCode;
import com.cmsz.xpay.common.exception.UpaySystemException;


/**
 * 银联签名验签加密操作证书工具
 * @author Eshin
 *
 */

@Component("certUtil")
public class CertUtil {
	
	/** 
	 * 验证签名公钥证书目录. 默认为空 
	 */
	@Value("${unionpay.validateCert.dir:}")
	private String validateCertDir;
	/**
	 * 证书使用模式(单证书/多证书)
	 * 单证书模式(系统运行时初始化指定证书)
	 * 多证书模式(业务进行时再加载所需证书)
	 */
	@Value("${unionpay.singleMode}")
	private String singleMode;
	/** 
	 * 签名证书路径. 默认为空
	 */
	@Value("${unionpay.signCert.path:}")
	private String signCertPath;
	/** 
	 * 签名证书密码.
	 */
	@Value("${unionpay.signCert.pwd}")
	private String signCertPwd;
	/** 
	 * 签名证书类型. 
     */
	@Value("${unionpay.signCert.type}")
	private String signCertType;
	/** 
	 * 证书容器. 
	 */
	private  KeyStore keyStore ;	
	/** 
	 * 验证签名证书.
	 */
	private  X509Certificate validateCert;
	/** 
	 * 验签证书存储Map. 
	 */
	private Map<String, X509Certificate> validateCertMap = new HashMap<String, X509Certificate>();
	/** 
	 * 根据传入证书文件路径和密码读取指定的证书容器.(一种线程安全的实现方式)
	 */
	private final ThreadLocal<KeyStore> certKeyStoreLocal = new ThreadLocal<KeyStore>();
	/** 
	 * 基于Map存储多商户RSA私钥 
	 */
	private final Map<String, KeyStore> certKeyStoreMap = new ConcurrentHashMap<String, KeyStore>();
	/** 
	 * 系统运行日志
	 */
	/**
	 * 初始化所有证书.
	 * 
	 */
	@PostConstruct
	public  void init(){
		//初始化证书目录路径
		initCertPath();
		
		if (UnionPayConstants.TRUE_STRING.equals(singleMode)) {
			// 单证书模式,初始化配置文件中的签名证书
			initSignCert();
		}
		// 初始化所有的验签证书
		initValidateCertFromDir();
	}

	/**初始化证书路径
	 * 
	 */
	private void initCertPath(){
		// WEB-INF对应的路径
		String web_infPath = new File(getClass().getResource("/").getPath()).getParent();

		if (StringUtils.isNotBlank(validateCertDir)) {
			validateCertDir = web_infPath + File.separator + validateCertDir;
		}
		if (StringUtils.isNotBlank(signCertPath)) {
			signCertPath = web_infPath + File.separator + signCertPath;
		}
		
	
	}

	/**
	 * 加载签名证书
	 */
	public  void initSignCert() {
		if (StringUtils.isBlank(signCertPath)){
			return;
		}
		try {
			keyStore = getKeyInfo(signCertPath,signCertPwd, signCertType);
		} catch (Exception e) {
			throw new UpaySystemException("初始化签名证书keyStore失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}

	/**
	 * 根据传入的证书文件路径和证书密码加载指定的签名证书
	 * @param certFilePath
	 *              签名证书路径
	 * @param certPwd
	 *              签名证书密码
	 * @param certTpye
	 *             签名证书类型（jks/pksc12）
	 */
	public void initSignCertByPath(String certFilePath, String certPwd,String certTpye) {
		certKeyStoreLocal.remove();
		File files = new File(certFilePath);
		if (!files.exists()) {
			return;
		}
		try {
			certKeyStoreLocal.set(getKeyInfo(certFilePath, certPwd, certTpye));
		} catch (Exception e) {
			throw new UpaySystemException("加载指定签名证书失败:"+e.getMessage(),
					                       DefaultReturnCode.UPAY_015A03,e);
		}

	}
	

	/**
	 * 加载RSA签名证书
	 * 
	 * @param certFilePath
	 * @param certPwd
	 * @param certType
	 */
	public  void loadRsaCertToMap(String certFilePath, String certPwd,String certType) {
		KeyStore keyStore = null;
		try {
			keyStore = getKeyInfo(certFilePath, certPwd, certType);
			certKeyStoreMap.put(certFilePath, keyStore);
		} catch (Exception e) {
			throw new UpaySystemException("加载RSA签名证书失败:"+e.getMessage(), 
					                       DefaultReturnCode.UPAY_015A03,e);
		}
	}
	
	/**
	 * 从指定目录下加载验证签名证书
	 * 
	 */
	private  void initValidateCertFromDir() {
		validateCertMap.clear();
		if (StringUtils.isBlank(validateCertDir)) {
			return;
		}
		CertificateFactory cf = null;
		FileInputStream in = null;
		try {
			cf = CertificateFactory.getInstance("X.509");
			File fileDir = new File(validateCertDir);
			File[] files = fileDir.listFiles(new CerFilter());
			if(null == files ||UnionPayConstants.ZERO == files.length ){
				throw new UpaySystemException("验签证书文件不存在", DefaultReturnCode.UPAY_015A03);
			}
			for (int i = 0; i < files.length; i++) {
				File file = files[i];
				in = new FileInputStream(file.getAbsolutePath());
				validateCert = (X509Certificate) cf.generateCertificate(in);
//				跳过过期证书，只打印对应告警日志
				boolean isExpire = checkExpired(new Date(),validateCert,file.getName());
				if(isExpire){
					continue;
				}
				validateCertMap.put(validateCert.getSerialNumber().toString(),validateCert);
			}
		} catch (Exception e) {
			throw new UpaySystemException("加载验证签名证书失败:"+e.getMessage(),
					                      DefaultReturnCode.UPAY_015A03,e);
		} finally {
			if (null != in) {
				try {
					in.close();
				} catch (IOException e) {
				}
		}
				}
	}

	/**
	 * 验证证书是否失效
	 * @param date
	 * @param validateCert
	 * @param fileName 
	 * @return
	 */
	private  boolean checkExpired(Date date, X509Certificate validateCert, String fileName) {
		boolean isExpire = false;
		try {
			validateCert.checkValidity(date);
		} catch (Exception e) {
			isExpire = true;
		}
		return isExpire;
	}

	/**
	 * 获取签名证书私钥（单证书模式）
	 * 
	 * @return
	 */
	public PrivateKey getSignCertPrivateKey() {
		try {
			Enumeration<String> aliasenum = keyStore.aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias,signCertPwd.toCharArray());
			return privateKey;
		} catch (Exception e) {
			throw new UpaySystemException("获取单证书签名证书私钥失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}

	/**
     * 通过传入证书绝对路径和证书密码获取所对应的签名证书私钥
	 * @param certPath
	 *             证书绝对路径
	 * @param certPwd
	 *             证书密码 
	 * @param certTpye
	 *             证书私钥
	 * @return
     * @deprecated
	 */
	public  PrivateKey getSignCertPrivateKeyByThreadLocal(
			String certPath, String certPwd,String certTpye) {
		if (null == certKeyStoreLocal.get()) {
			// 初始化指定certPath和certPwd的签名证书容器
			initSignCertByPath(certPath, certPwd,certTpye);
		}
		try {
			Enumeration<String> aliasenum = certKeyStoreLocal.get().aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			PrivateKey privateKey = (PrivateKey) certKeyStoreLocal.get()
					                .getKey(keyAlias, certPwd.toCharArray());
			return privateKey;
		} catch (Exception e) {
			throw new UpaySystemException("获取签名证书的私钥失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}
	
	/**
	 * 通过传入证书绝对路径和证书密码获取所对应的签名证书私钥
	 * @param certPath
	 * @param certPwd
	 * @param certType
	 * @return 签名私钥
	 */
	public  PrivateKey getSignCertPrivateKeyByStoreMap(String certPath,
			String certPwd,String certType) {
		if (!certKeyStoreMap.containsKey(certPath)) {
			loadRsaCertToMap(certPath, certPwd,certType);
		}
		try {
			Enumeration<String> aliasenum = certKeyStoreMap.get(certPath).aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			PrivateKey privateKey = (PrivateKey) certKeyStoreMap.get(certPath)
					                .getKey(keyAlias, certPwd.toCharArray());
			return privateKey;
		} catch (Exception e) {
			throw new UpaySystemException("获取签名证书的私钥失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}

	/**
	 * 验证签名证书
	 * 
	 * @return 验证签名证书的公钥
	 */
	public PublicKey getValidateKey() {
		if (null == validateCert) {
			return null;
		}
		return validateCert.getPublicKey();
	}

	/**
	 * 通过certId获取证书Map中对应证书的公钥
	 * 
	 * @param certId
	 *            证书物理序号
	 * @return 通过证书编号获取到的公钥
	 */
	public PublicKey getValidateKey(String certId) {
		X509Certificate cf = null;
		if (validateCertMap.containsKey(certId)) {
			// 存在certId对应的证书对象
			cf = validateCertMap.get(certId);
			return cf.getPublicKey();
		} else {
			// 不存在则重新Load证书文件目录
			initValidateCertFromDir();
			if (validateCertMap.containsKey(certId)) {
				// 存在certId对应的证书对象
				cf = validateCertMap.get(certId);
				return cf.getPublicKey();
			} else {
				throw new UpaySystemException("缺少certId对应验签证书,获取验签公钥失败:", 
						                      DefaultReturnCode.UPAY_015A03);
			}
		}
	}

	

	/**
	 * 获取签名证书中的证书序列号（单证书）
	 * 
	 * @return 证书的物理编号
	 */
	public String getSignCertId() {
		try {
			Enumeration<String> aliasenum = keyStore.aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
			cert.checkValidity(new Date());
			return cert.getSerialNumber().toString();
		} catch (Exception e) {
			throw new UpaySystemException("单证书获取签名证书的序列号失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}


	/**
	 * 获取签名证书公钥对象
	 * 单证书模式
	 * @return  证书公钥
	 */
	public PublicKey getSignPublicKey() {
		try {
			Enumeration<String> aliasenum = keyStore.aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements())
			{
				keyAlias = (String) aliasenum.nextElement();
			}
			X509Certificate cert = (X509Certificate)keyStore.getCertificate(keyAlias);
			cert.checkValidity(new Date());
			PublicKey pubkey = cert.getPublicKey();
			return pubkey;
		} catch (Exception e) {
			throw new UpaySystemException("获取签名证书公钥对象失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}
	
	
	/**
	 * 将证书文件读取为证书存储对象
	 * @return 证书对象
	 * @param keyStorefile
	 *                证书文件名
	 * @param keypwd
	 *            证书密码
	 * @param type
	 *            证书类型
	 * @return    keystore 对象
	 */
	public KeyStore getKeyInfo(String keyStorefile, String keypwd, String type){
		FileInputStream fis = null;
		try {
			KeyStore ks = null;
			if (UnionPayConstants.CERT_TYPE_JKS.equals(type)) {
				ks = KeyStore.getInstance(type);
			} else if (UnionPayConstants.CERT_TYPE_PKCS12.equals(type)) {
				String jdkVendor = System.getProperty("java.vm.vendor");
				if (null != jdkVendor && jdkVendor.startsWith("IBM")) {
					// 如果使用IBMJDK,则强制设置BouncyCastleProvider的指定位置,解决使用IBMJDK时兼容性问题
					Security.insertProviderAt(
							new org.bouncycastle.jce.provider.BouncyCastleProvider(),
							1);
				}else{
					Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
				}
				ks = KeyStore.getInstance(type);
			}
			fis = new FileInputStream(keyStorefile);
			char[] nPassword = null;
			nPassword = null == keypwd || "".equals(keypwd.trim()) ? null: keypwd.toCharArray();
			if (null != ks) {
				ks.load(fis, nPassword);
			}
			return ks;
		} catch (Exception e) {
			throw new UpaySystemException("将证书文件读取为证书KeyStore失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A06, e);
		}finally{
			if(null!=fis)
				try {
					fis.close();
				} catch (IOException e) {
				}
		}
	}
	

	/**
	 * 证书文件过滤器
	 * 
	 */
	static class CerFilter implements FilenameFilter {
		public boolean isCer(String name) {
			if (name.toLowerCase().endsWith(".cer")) {
				return true;
			} else {
				return false;
			}
		}
		public boolean accept(File dir, String name) {
			return isCer(name);
		}
	}
	
	/**
	 * 从一个ThreadLocal中获取当前KeyStore中的CertId,
	 * 如果获取失败则重新初始化这个KeyStore并存入ThreadLocal
	 * @deprecated
	 * @param certPath
	 * @param certPwd
	 * @param certTpye
	 * @return 证书物理编号
	 */
	public  String getCertIdByThreadLocal(String certPath, String certPwd,String certTpye) {
		// 初始化指定certPath和certPwd的签名证书容器
		initSignCertByPath(certPath, certPwd,certTpye);
		try {
			Enumeration<String> aliasenum = certKeyStoreLocal.get().aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			X509Certificate cert = (X509Certificate) certKeyStoreLocal.get().getCertificate(keyAlias);
			return cert.getSerialNumber().toString();
		} catch (Exception e) {
			throw new UpaySystemException("从证书容器中获取证书系列号失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}
	
	/**
	 * KeyStoreMap中获取证书CertId
	 * @param certPath
	 * @param certPwd
	 * @param certTpye
	 * @return  证书物理编号
	 */
	public  String getCertIdByKeyStoreMap(String certPath, String certPwd,String certTpye) {
		if (!certKeyStoreMap.containsKey(certPath)) {
			// 缓存中未查询到,则加载RSA证书
			loadRsaCertToMap(certPath, certPwd,certTpye);
		}
		return getCertIdIdByStore(certKeyStoreMap.get(certPath));
	}

	private static String getCertIdIdByStore(KeyStore keyStore) {
		Enumeration<String> aliasenum = null;
		try {
			aliasenum = keyStore.aliases();
			String keyAlias = null;
			if (aliasenum.hasMoreElements()) {
				keyAlias = aliasenum.nextElement();
			}
			X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
			cert.checkValidity(new Date());
			return cert.getSerialNumber().toString();
		} catch (Exception e) {
			throw new UpaySystemException("从keystore中获取证书系列号失败:"+e.getMessage(), 
					                      DefaultReturnCode.UPAY_015A03,e);
		}
	}
	

	/**
	 * 获取证书容器
	 * 
	 * @return 
	 */
	public Map<String, X509Certificate> getCertMap() {
		return validateCertMap;
	}

	/**
	 * 设置证书容器
	 * 
	 * @param certMap
	 */
	public void setCertMap(Map<String, X509Certificate> certMap) {
		this.validateCertMap = certMap;
	}
	
	
}
